using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;

using NConfigGenerator;

namespace NConfigGeneratorUI
{
    public partial class ConfigCodeGenForm : Form
    {
        public ConfigCodeGenForm()
        {
            InitializeComponent();
        }

        #region Config tree TreeView management
        private void GenerateConfigTree()
        {
            tvConfigTree.BeginUpdate();
            try
            {
                tvConfigTree.Nodes.Clear();

                XmlDocument xdoc = OpenXmlDocument(txtInputXml.Text);
                if (xdoc == null)
                    return;
                ConfigTree configTree = ConfigTree.InferFromXml(txtInputXml.Text);
                if (configTree == null)
                    return;
                tvConfigTree.Tag = configTree;

                TreeNode rootNode = CreateConfigTreeNode(configTree.RootNode);
                tvConfigTree.Nodes.Add(rootNode);
                AddNodes(configTree.RootNode, rootNode);
                tvConfigTree.ExpandAll();
            } finally
            {
                tvConfigTree.EndUpdate();
            }
        }

        private static TreeNode CreateConfigTreeNode(ConfigNode configNode)
        {
            var treeNode = new TreeNode(configNode.Name)
                           { Tag = configNode, ImageKey = configNode is ElementConfigNode ? "Element" : "Collection" };
            treeNode.SelectedImageKey = treeNode.ImageKey;
            return treeNode;
        }

        private static void AddNodes(ConfigNode parentConfigNode, TreeNode parentTreeNode)
        {
            foreach (ConfigNode childNode in parentConfigNode.AllChildren)
            {
                TreeNode childTreeNode = CreateConfigTreeNode(childNode);
                parentTreeNode.Nodes.Add(childTreeNode);
                AddNodes(childNode, childTreeNode);
            }
        }
        #endregion

        private void GenerateCode()
        {
            if (tvConfigTree.Nodes.Count == 0)
                GenerateConfigTree();
            if (cbxLanguage.SelectedIndex < 0)
                cbxLanguage.SelectedIndex = 0;
            txtOutput.Text = string.Empty;
            if (tvConfigTree.Nodes.Count > 0)
            {
                var configTree = (ConfigTree)tvConfigTree.Tag;
                var options = new GeneratorOptions {
                    Language = cbxLanguage.Text,
                    Namespace = txtNamespace.Text,
                    ConfigClassName = txtConfigClassName.Text,
                    ConfigPropertyName = txtConfigProperyName.Text
                };
                string code = Generator.Generate(configTree, options);
                txtOutput.Text = code;
            }
        }

        #region XML helpers
        private static XmlDocument OpenXmlDocument(string xml)
        {
            var xdoc = new XmlDocument();
            try
            {
                xdoc.LoadXml(xml);
                return xdoc;
            } catch (XmlException)
            {
                return null;
            }
        }

        private static bool IsWellFormedXml(string xml)
        {
            return OpenXmlDocument(xml) != null;
        }
        #endregion

        private void OnFormLoaded(object sender, EventArgs e)
        {
            //Initialize controls
            lblInformation.Text = tabsMain.SelectedTab.Tag as string;
            tsInput.ImageList = ilMisc;
            tlblInputStatus.ImageKey = "InvalidXML";

            //Remove 2nd and 3rd tabs until the input XML in the first tab is valid
            tabsMain.TabPages.Remove(tabOutput);
            tabsMain.TabPages.Remove(tabConfigTree);

            //Load sample configuration xmls by finding all embedded resources ending with ".xml"
            Assembly assembly = Assembly.GetEntryAssembly();
            IEnumerable<string> resourceNames = assembly.GetManifestResourceNames().Where(rn => rn.EndsWith(".xml"));
            foreach (string resourceName in resourceNames)
            {
                using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
                {
                    if (resourceStream == null)
                        continue;
                    using (var reader = new StreamReader(resourceStream))
                    {
                        string content = reader.ReadToEnd();
                        if (IsWellFormedXml(content))
                            tcbxSamples.Items.Add(new Sample(resourceName, content));
                    }
                }
            }

            //Populate the "Data Type" column in the attributes data grid, from the values of the
            //AttributeType enum
            Array attributeValues = Enum.GetValues(typeof(AttributeType));
            foreach (object attributeValue in attributeValues)
                colDataType.Items.Add(attributeValue);

            //Populate the languages combobox in the 3rd tab with all the available CodeDOM languages
            CompilerInfo[] compilerInfos = CodeDomProvider.GetAllCompilerInfo();
            foreach (CompilerInfo compilerInfo in compilerInfos)
            {
                if (compilerInfo.IsCodeDomProviderTypeValid)
                    cbxLanguage.Items.Add(compilerInfo.GetLanguages()[0]);
            }
        }

        private void OnTabChanged(object sender, EventArgs e)
        {
            var information = tabsMain.SelectedTab.Tag as string;
            lblInformation.Text = !string.IsNullOrEmpty(information) ? information : string.Empty;

            if (tabsMain.SelectedTab == tabConfigTree)
                GenerateConfigTree();
            else if (tabsMain.SelectedTab == tabOutput)
                GenerateCode();
        }

        #region Sample XML tab events
        private void OnLoadButtonClicked(object sender, EventArgs e)
        {
            if (dlgOpenXML.ShowDialog(this) == DialogResult.OK)
                txtInputXml.Text = File.OpenText(dlgOpenXML.FileName).ReadToEnd();
        }

        private void OnPasteButtonClicked(object sender, EventArgs e)
        {
            txtInputXml.Text = Clipboard.GetText(TextDataFormat.Text);
        }

        private void OnInsertSampleButtonClicked(object sender, EventArgs e)
        {
            var sample = tcbxSamples.SelectedItem as Sample;
            if (sample != null)
                txtInputXml.Text = sample.Content;
        }

        private void OnInputXmlChanged(object sender, EventArgs e)
        {
            bool isWellFormedXml = IsWellFormedXml(txtInputXml.Text);
            if (isWellFormedXml)
            {
                tlblInputStatus.ImageKey = "ValidXML";
                tlblInputStatus.Text = "Valid XML";
                if (tabsMain.TabPages.Count == 1)
                {
                    tabsMain.TabPages.Add(tabConfigTree);
                    tabsMain.TabPages.Add(tabOutput);
                }
            } else
            {
                tlblInputStatus.ImageKey = "InvalidXML";
                tlblInputStatus.Text = "Invalid XML";
                if (tabsMain.TabPages.Count == 3)
                    tabsMain.TabPages.Remove(tabOutput);
                tabsMain.TabPages.Remove(tabConfigTree);
            }
        }
        #endregion

        #region Tweak data types tab events
        private void OnConfigTreeNodeSelected(object sender, TreeViewEventArgs e)
        {
            var configNode = (ConfigNode)e.Node.Tag;
            if (configNode != null)
                dataGridView1.DataSource = configNode.Attributes;
        }
        #endregion

        #region Output tab events
        private void OnOutputOptionChanged(object sender, EventArgs e)
        {
            GenerateCode();
        }

        private void OnSaveButtonClicked(object sender, EventArgs e)
        {
            CompilerInfo compilerInfo = CodeDomProvider.GetCompilerInfo(cbxLanguage.Text);
            string firstExtension = compilerInfo.GetExtensions()[0];
            string defaultExtension = firstExtension.StartsWith(".") ? firstExtension : "." + firstExtension;
            dlgSaveCode.DefaultExt = defaultExtension;
            dlgSaveCode.Filter = string.Format("{0} files|*{1}", compilerInfo.GetLanguages()[0], defaultExtension);
            if (dlgSaveCode.ShowDialog(this) == DialogResult.OK)
                File.CreateText(dlgSaveCode.FileName).Write(txtOutput.Text);
        }

        private void OnCopyButtonClicked(object sender, EventArgs e)
        {
            Clipboard.SetText(txtOutput.Text);
        }
        #endregion
    }
}